/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */


package pkg4funparse;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Stack;
import pkg4fungeneric.Code;
import pkg4funlex.Analyzer;
import pkg4funlex.Token;
import pkg4funparse.Symbol.Types;

public class RecursiveAnalyser {

    final static int iPROGRAM = 0;
    final static int iCOMMAND = 1;
    final static int iCOMMAND_ = 2;
    final static int iIF = 3;
    final static int iDO = 4;
    final static int iFUNC_INTR = 5;
    final static int iWRITE = 6;
    final static int iSAIDA_WRITE = 7;
    final static int iREAD = 8;
    final static int iATRIB = 9;

    final static String sPROGRAM = "<PROGRAM>";
    final static String sENDPROGRAM = "ENDPROGRAM";
    final static String sID = "id";
    final static String sCOMMAND = "<COMMAND>";
    final static String sCOMMAND_ = "<CONTROLE_INTRINSC>";
    final static String sIF = "<IF>";
    final static String sENDIF = "ENDIF";
    final static String sDO = "<DO>";
    final static String sENDDO = "ENDDO";
    final static String sFUNC_INTR = "<FUNC_INTR>";
    final static String sREAD = "<READ>";
    final static String sWRITE = "<WRITE>";
    final static String sSAIDA_WRITE = "<SAIDA_WRITE>";
    final static String sATRIB = "<ATRIBUI>";
    final static String sINTEGER = "token_LitInteger";

    private static ArrayList<ArrayList<Symbol>> RULES[];
    private static Stack<Symbol> ss;
    private static Symbol current_token, error_symbol, error_token, LAMBDA, END;
    private static Token tk;
    private static int sp;
    private static HashMap<String,Symbol> tkSymb;
    private static String FIM_DE_ENTRADA = "$";

    private static Symbol symb(Types t, String val) {
        if(tkSymb.containsKey(val))
                return tkSymb.get(val);
        Symbol s = new Symbol(t, val);
        int n = Data.SYMBOLS.size();
        Data.SYMBOLS.add(s);
        tkSymb.put(val, s);
        return s;
    }

    private static boolean match(Symbol s) {
        if (s.value.equals(current_token.value)) {
            nextSymbol();
            return true;
        }
        error_symbol = s;
        error_token = current_token;
        return false;
    }

    private static void nextSymbol() {
       // System.out.println("==pre== tk = "+current_token+" sp: "+sp);
       // System.out.println("sz(ss): "+ss.size()+" ss: "+ss);
        sp++;
       // if(current_token.value.equals("ENDIF"))
       //     System.out.println("UI");
        if (sp == ss.size()) {
            tk = Analyzer.getNextToken();
            current_token = tkSymb.get(tk.getType());
            ss.push(current_token);
            
        } else {
           
            current_token = ss.get(sp);
        }
  //      System.out.println("==pos== tk = "+current_token+" sp: "+sp);;
    //    System.out.println("sz(ss): "+ss.size()+" ss: "+ss+"\n");
    }

    private static void nodePrint(int start, String where) {
        System.out.print(where+" -> ");
        for(int i = start; i < sp; i++)
            System.out.print(ss.get(i).value+" ");
        System.out.print("\n");
        while(start < sp) {
            sp--;
            ss.remove(sp);
        }
    }
//    PROGRAM testerecursivo
//
//WRITE(*,*) teste
//
//ENDPROGRAM testerecursivo
    
    private static boolean program(int startPos) {
        boolean matched = true;
        ArrayList<Symbol> rules = RULES[iPROGRAM].get(0);
        matched = 
              match( rules.get(0) )/*   "PROGRAM"   */
           && match( rules.get(1) )/*     "ID"      */
           && command( sp )        /*   "COMMAND"   */
           && match( rules.get(3) )/*  "ENDPROGRAM" */
           && match( rules.get(4) )/*     "ID"      */
           && (tk.getType().equals(FIM_DE_ENTRADA));
        if(!matched) erro(sPROGRAM);
        else nodePrint(startPos, sPROGRAM);
        return matched;
    }

    private static boolean command(int startPos) {
        Symbol cur_sym;
        boolean matched = false;
        
        for(int i = 0; i < RULES[iCOMMAND].size(); i++) {
            matched = true;
            sp = startPos;
            ArrayList<Symbol> symbols = (ArrayList<Symbol>) RULES[iCOMMAND].get(i);
            for(int j = 0; j < symbols.size(); j++) {
                if(!matched) break;
                cur_sym = symbols.get(j);
                if(cur_sym.type == Types.TERMINAL) {
                    matched = match(cur_sym);
                }
                else {
                    if(cur_sym.value.equals(sCOMMAND_)) matched = command_(sp);
                    else if(cur_sym.value.equals(sCOMMAND)) matched = command(sp);
                }
                if(!matched) break;
            }
            if(matched) break;
        }
        if(matched) nodePrint(startPos, sCOMMAND);
        return matched;
    }

    private static boolean command_(int startPos) {
        Symbol cur_sym;
        boolean matched = false;
          for(int i = 0; i < RULES[iCOMMAND_].size(); i++) {
            matched = true;
            ArrayList<Symbol> symbols = RULES[iCOMMAND_].get(i);
            for(int j = 0; j < symbols.size(); j++) {
                cur_sym = symbols.get(j);
                if (cur_sym.value.equals(sIF))  matched = If(sp);
                else  if(cur_sym.value.equals(sDO))  matched = Do(sp);
                else  if(cur_sym.value.equals(sFUNC_INTR))  matched = func_intr(sp);
                else if(cur_sym.value.equals(sWRITE)) matched = func_write(sp);
                else if(cur_sym.value.equals(sREAD)) matched = func_read(sp);
                if(!matched) break;
            }
            if(matched) break;
        }
        if(matched) nodePrint(startPos, sCOMMAND_);
        return matched;
    }

    private static boolean func_intr(int startPos) {
        boolean matched = false;
        int flag = 0;
        ArrayList <ArrayList<Symbol>> rule = RULES[iFUNC_INTR];
        for(int x=0 ; x < rule.size() ; x++) {
            ArrayList <Symbol> rulex = (ArrayList <Symbol>) rule.get(x);
            matched = true;
            sp = startPos;
            for(int j = 0; j < rulex.size()  ; j++) {
                flag = j;
                Symbol s = rulex.get(j); 
                if(s.type.equals(Types.TERMINAL)) {
                    matched = match(s); 
                    if(!matched) { if (flag > 0) erro(sFUNC_INTR); break; }
                }
            }
            if(matched) break;
        }
        if(matched) nodePrint(startPos, sFUNC_INTR);
        return matched;
    }

//----<PROGRAM> @ id <COMMAND> ENDPROGRAM id
//----<COMMAND> @ <CONTROLE_INTRINSC> <COMMAND> | %
//-----<CONTROLE_INTRINSC> @ <IF> | <DO> | <FUNC_INTR> | <READ> | <WRITE>
//------<IF> @ IF ( <EXPR_LOG> ) <COMMAND> ENDIF
//-----<FUNC_INTRINS> @ SQRT ( <EXPR_ARIT> ) | POW ( <EXPR_ARIT> , <EXPR_ARIT> ) | MOD (  <EXPR_ARIT> , <EXPR_ARIT> )
//------<DO> @  DO id ( <EXPR_ARIT> ) , ( <EXPR_ARIT> ) , ( token_LitInteger ) <COMMAND> ENDDO
//<WRITE> @ WRITE ( tokenAst , tokenAst ) <SAIDA_WRITE>
//<SAIDA WRITE> @ id | token_LitString
//<READ> @ READ ( tokenAst , tokenAst ) id*
    private static boolean func_write(int startPos) {
        boolean matched = true;
        int flag = 0;
        ArrayList <ArrayList <Symbol>> rules = RULES[iWRITE];
        for(int x=0; x < rules.size(); x++) {
            ArrayList<Symbol> rulex = rules.get(x);
            matched = true;
            sp = startPos;
            for(int j = 0; j < rulex.size(); j++) {
                flag = j;
                Symbol s = rulex.get(j);
                if(s.type.equals(Types.TERMINAL)) {
                    matched = match(s);
                    if(!matched) { if (flag > 0) erro(sWRITE); break; }
                } else {
                    func_saidawrite(sp);
                }
            }
            if(matched) break;
        }
        if(matched) nodePrint(startPos, sWRITE);
        return matched;
    }

    private static boolean func_saidawrite(int startPos) {
        boolean matched = false;
        ArrayList<ArrayList <Symbol>> rule = RULES[iSAIDA_WRITE];
        for(int x=0 ; x < rule.size() ; x++) {
            ArrayList<Symbol> rulex = rule.get(x);
            matched = true;
            sp = startPos;
            for(int j = 0; j < rulex.size(); j++) {
                Symbol s = rulex.get(j);
                if(s.type.equals(Types.TERMINAL)) {
                    matched = match(s);
                    if(!matched) break;
                }
            }
            if(matched) break;
        }
        if(matched) nodePrint(startPos, sSAIDA_WRITE);
        else erro(sSAIDA_WRITE);
        return matched;
    }

    private static boolean func_read(int startPos) {
        boolean matched = true;
        int flag = 0;
        ArrayList<ArrayList <Symbol>> rule = RULES[iREAD];
        for(int x=0 ; x < rule.size() ; x++) {
            ArrayList<Symbol> rulex = rule.get(x);
            matched = true;
            sp = startPos;
            for(int j = 0; j < rulex.size(); j++) {
                Symbol s = rulex.get(j);
                flag = j;
                if(s.type.equals(Types.TERMINAL)) {
                    matched = match(s);
                    if(!matched) { if (flag > 0) erro(sREAD); break; }
                }
            }
            if(matched) break;
        }
        if(matched) nodePrint(startPos, sREAD);
        
        return matched;
    }

    private static boolean If(int startPos) {
        ArrayList <Symbol> rulex = (ArrayList <Symbol>) RULES[iIF].get(0);
     //   System.out.println("entrou em "+sIF);
        boolean matched =
                   match(rulex.get(0)) /*    "IF"    */
                && match(rulex.get(1)) /*    "("     */
                && match(rulex.get(2)) /* "EXPR_LOG" */
                && match(rulex.get(3)) /*     ")"    */
                && command(sp)         /*  "COMMAND" */
                && match(rulex.get(5)); /*  "ENDIF"  */
        
        if(matched) nodePrint(startPos, sIF);
        else { if (sp > startPos) erro(sIF); }
        return matched;
    }

    private static boolean Do(int startPos) {
        ArrayList <Symbol> rulex = (ArrayList <Symbol>) RULES[iDO].get(0);
        boolean matched =
                   match(rulex.get(0)) /*    "DO"   */
                && match(rulex.get(1)) /*    "ID"   */
                && match(rulex.get(2)) /*    "("    */
                && match(rulex.get(3)) /* "INTEGER" */
                && match(rulex.get(4)) /*     ")"   */
                && match(rulex.get(5)) /*    ","    */
                && match(rulex.get(6)) /*    "("    */
                && match(rulex.get(7)) /* "INTEGER" */
                && match(rulex.get(8)) /*     ")"   */
                && match(rulex.get(9)) /*     ","   */
                && match(rulex.get(10)) /*   "("    */
                && match(rulex.get(11)) /* "INTEGER"*/
                && match(rulex.get(12)) /*   ")"    */
                && command(sp)         /* "COMMAND" */
                && match(rulex.get(14));/* "ENDDO"  */


        if(matched) nodePrint(startPos, sDO);
        else  if (sp > startPos) erro(sDO); 
        return matched;
    
    }

//<FUNC_INTRINS> @ SQRT ( <EXPR_ARIT> ) | POW ( <EXPR_ARIT> , <EXPR_ARIT> ) | MOD (  <EXPR_ARIT> , <EXPR_ARIT> )

    private static void erro(String prod) {
        System.out.println("Erro sintatico, produção: " + prod + " linha:" + tk.getLinenum() + " esperado: " + error_symbol.value);
        if(!error_token.value.equals("$")) System.out.println("encontrado: " + error_token.value);
        System.exit(0);
    }

    public static void init() {
        start();
        program(0);
        // nivel 0: PROGRAM <id> <decl> <command> <subroutine> END PROGRAM <id>

    }

    private static void start() {

        LAMBDA = new Symbol(Types.TERMINAL, Code.LAMBDA);
        END = new Symbol(Types.TERMINAL, Code.END);
        tkSymb = Data.tkSymb;

        @SuppressWarnings("unchecked")
        ArrayList<ArrayList<Symbol>> rules[] = new ArrayList[10];
        for(int i = 0; i < 10; i++)
        {
            rules[i] = new ArrayList<ArrayList<Symbol>>();
        }
        RULES = rules;
        ss = new Stack<Symbol>();
        sp = 0;
        current_token = tkSymb.get(Analyzer.getNextToken().getType());
        ss.push(current_token);
        
        /* Regras do não terminal PROGRAM */
        Symbol programRules[] = {
            symb(Types.TERMINAL, "PROGRAM"),
            symb(Types.TERMINAL, sID),
            symb(Types.N_TERMINAL, sCOMMAND),
            symb(Types.TERMINAL, sENDPROGRAM),
            symb(Types.TERMINAL, sID)};
        RULES[iPROGRAM].add(new ArrayList<Symbol>(Arrays.asList(programRules)));

        /* Regras do não terminal COMMAND */
        Symbol commandRules[] = {
            symb(Types.N_TERMINAL, sCOMMAND_),
            symb(Types.N_TERMINAL, sCOMMAND)};
        RULES[iCOMMAND].add(new ArrayList<Symbol> (Arrays.asList(commandRules)));
        RULES[iCOMMAND].add(new ArrayList<Symbol>());

        /* Regras do não terminal COMMAND_ */
        RULES[iCOMMAND_].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.N_TERMINAL, sWRITE))));
        RULES[iCOMMAND_].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.N_TERMINAL, sIF))));
        RULES[iCOMMAND_].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.N_TERMINAL, sDO))));
        RULES[iCOMMAND_].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.N_TERMINAL, sFUNC_INTR))));
        RULES[iCOMMAND_].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.N_TERMINAL, sREAD))));

        /* Regras do não terminal IF */
        RULES[iIF].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "IF"),
                symb(Types.TERMINAL,"("),
                symb(Types.TERMINAL, sID),
                symb(Types.TERMINAL, ")"),
                symb(Types.N_TERMINAL, sCOMMAND),
                symb(Types.TERMINAL, sENDIF))));

        /* Regras do não terminal DO */
        RULES[iDO].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "DO"),
                symb(Types.TERMINAL, sID),
                symb(Types.TERMINAL, "("),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ")"),
                symb(Types.TERMINAL, ","),
                symb(Types.TERMINAL, "("),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ")"),
                symb(Types.TERMINAL, ","),
                symb(Types.TERMINAL, "("),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ")"),
                symb(Types.N_TERMINAL, sCOMMAND),
                symb(Types.TERMINAL, sENDDO))));

        /* Regras do não terminal FUNC_INTRINS */
        RULES[iFUNC_INTR].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "SQRT"),
                symb(Types.TERMINAL, "("),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ")"))));
        RULES[iFUNC_INTR].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "POW"),
                symb(Types.TERMINAL, "("),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ","),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ")"))));
        RULES[iFUNC_INTR].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "MOD"),
               symb(Types.TERMINAL, "("),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ","),
                symb(Types.TERMINAL, sINTEGER),
                symb(Types.TERMINAL, ")"))));

        /* Regras do não terminal WRITE */
        RULES[iWRITE].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "WRITE"),
                symb(Types.TERMINAL,"("),
                symb(Types.TERMINAL, "*"),
                symb(Types.TERMINAL, ","),
                symb(Types.TERMINAL, "*"),
                symb(Types.TERMINAL, ")"),
                symb(Types.N_TERMINAL, "<SAIDA_WRITE>"))));

        /* Regras do não terminal SAIDA WRITE */
        RULES[iSAIDA_WRITE].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.TERMINAL, sID))));
        RULES[iSAIDA_WRITE].add(new ArrayList<Symbol>(Arrays.asList(symb(Types.TERMINAL, "token_LitString"))));

        /* Regras do não terminal READ */
        RULES[iREAD].add(new ArrayList<Symbol>(Arrays.asList(
                symb(Types.TERMINAL, "READ"),
                symb(Types.TERMINAL,"("),
                symb(Types.TERMINAL, "*"),
                symb(Types.TERMINAL, ","),
                symb(Types.TERMINAL, "*"),
                symb(Types.TERMINAL, ")"),
                symb(Types.TERMINAL, sID))));
    }
}
   /*
<EXPR> @ <TERMO> <SOMA>
<SOMA> @ + <TERMO> <SOMA> | - <TERMO> <SOMA> | %
<TERMO> @ <FATOR> <PRODUTO>
<PRODUTO> @ * <FATOR> <PRODUTO> | / <FATOR> <PRODUTO> | %
<FATOR> @ <NUMERO> | <IDENTIFICADOR> <FUNC_ID> | ‘(‘<EXPR>’)’ | <FUNC_INTRINS>
     */
    /*
     typedef enum {ident, number, lparen, rparen, times, slash, plus,
    minus, eql, neq, lss, leq, gtr, geq, callsym, beginsym, semicolon,
    endsym, ifsym, whilesym, becomes, thensym, dosym, constsym, comma,
    varsym, procsym, period, oddsym} Symbol;

Symbol sym;
void getsym(void);
void error(const char msg[]);
void expression(void);

int accept(Symbol s) {
    if (sym == s) {
        getsym();
        return 1;
    }
    return 0;
}

int expect(Symbol s) {
    if (accept(s))
        return 1;
    error("expect: unexpected symbol");
    return 0;
}

void factor(void) {
    if (accept(ident)) {
        ;
    } else if (accept(number)) {
        ;
    } else if (accept(lparen)) {
        expression();
        expect(rparen);
    } else {
        error("factor: syntax error");
        getsym();
    }
}

void term(void) {
    factor();
    while (sym == times || sym == slash) {
        getsym();
        factor();
    }
}

void expression(void) {
    if (sym == plus || sym == minus)
        getsym();
    term();
    while (sym == plus || sym == minus) {
        getsym();
        term();
    }
}

void condition(void) {
    if (accept(oddsym)) {
        expression();
    } else {
        expression();
        if (sym == eql || sym == neq || sym == lss || sym == leq || sym == gtr || sym == geq) {
            getsym();
            expression();
        } else {
            error("condition: invalid operator");
            getsym();
        }
    }
}

void statement(void) {
    if (accept(ident)) {
        expect(becomes);
        expression();
    } else if (accept(callsym)) {
        expect(ident);
    } else if (accept(beginsym)) {
        do {
            statement();
        } while (accept(semicolon));
        expect(endsym);
    } else if (accept(ifsym)) {
        condition();
        expect(thensym);
        statement();
    } else if (accept(whilesym)) {
        condition();
        expect(dosym);
        statement();
    }
}

void block(void) {
    if (accept(constsym)) {
        do {
            expect(ident);
            expect(eql);
            expect(number);
        } while (accept(comma));
        expect(semicolon);
    }
    if (accept(varsym)) {
        do {
            expect(ident);
        } while (accept(comma));
        expect(semicolon);
    }
    while (accept(procsym)) {
        expect(ident);
        expect(semicolon);
        block();
        expect(semicolon);
    }
    statement();
}

void program(void) {
    getsym();
    block();
    expect(period);
}
     */